package com.example.lotterysystem.service.mq;

import cn.hutool.core.date.DateUtil;
import com.example.lotterysystem.common.config.ExecutorConfig;
import com.example.lotterysystem.common.exception.ServiceException;
import com.example.lotterysystem.common.utils.JacksonUtil;
import com.example.lotterysystem.common.utils.MailUtil;
import com.example.lotterysystem.common.utils.SMSUtil;
import com.example.lotterysystem.controller.param.DrawPrizeParam;
import com.example.lotterysystem.dao.dataobject.ActivityPrizeDO;
import com.example.lotterysystem.dao.dataobject.WinningRecordDO;
import com.example.lotterysystem.dao.mapper.ActivityPrizeMapper;
import com.example.lotterysystem.dao.mapper.WinningRecordMapper;
import com.example.lotterysystem.service.DrawPrizeService;
import com.example.lotterysystem.service.activitystatus.ActivityStatusManager;
import com.example.lotterysystem.service.dto.ConvertActivityStatusDTO;
import com.example.lotterysystem.service.enums.ActivityPrizeStatusEnum;
import com.example.lotterysystem.service.enums.ActivityPrizeTiersEnum;
import com.example.lotterysystem.service.enums.ActivityStatusEnum;
import com.example.lotterysystem.service.enums.ActivityUserStatusEnum;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static com.example.lotterysystem.common.config.DirectRabbitConfig.QUEUE_NAME;

@Component
@RabbitListener(queues = QUEUE_NAME)
public class MqReceiver {
    private static final Logger logger = LoggerFactory.getLogger(MqReceiver.class);

    @Autowired
    private DrawPrizeService drawPrizeService;

    @Autowired
    private ActivityStatusManager activityStatusManager;

    @Autowired
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;

    @Autowired
    private MailUtil mailUtil;

    @Autowired
    private SMSUtil smsUtil;

    @Autowired
    private ActivityPrizeMapper activityPrizeMapper;

    @Autowired
    private WinningRecordMapper winningRecordMapper;


    @RabbitHandler
    public void process(Map<String ,String > message) {
        // 成功接收队列里的消息
        logger.info("MQ成功接收队列里的消息:{}",
                JacksonUtil.writeValueAsString(message));
        String paramString = message.get("messageData");
        DrawPrizeParam param = JacksonUtil.readValue(paramString,DrawPrizeParam.class);

        // 处理抽奖的流程

        try {
//            // 为了演示写一下
//            if (true) {
//                return;
//            }

            // 校验抽奖请求是否有效
            // 这边可能出现，前端发送了两次一样的抽奖请求
            // 此时对于param来说就是一样的两个请求
            // 如果此时是最后一个奖品，param1抽完之后param2再抽
            // param1 -》 活动已完成、奖品已完成
            // param2 -》 回滚活动、奖品状态
            // 就会触发回滚，导致奖品抽了也白抽，都回滚了
            if (!drawPrizeService.checkDrawPrizeParam(param)) {
                return;
            }

            // *********重要***********
            // 状态扭转处理（活动、奖品、人员状态）
            statusConvert(param);
            // 保存中奖者名单
            List<WinningRecordDO> winningRecordDOList =
                    drawPrizeService.saveWinnerRecords(param);

            // 通知中奖者（短信、邮箱）
            // 抽奖之后的流程，采用并发（异步）的方式来处理
            syncExecute(winningRecordDOList);
        } catch (ServiceException e) {
            logger.error("处理MQ消息异常！{}，{}",e.getCode(),e.getMsg(),e);
            // 如果发生异常，需要保证事务一致性（回滚）
            rollback(param);
            // 抛出异常：消息重试（解决异常：代码bug、网络问题、服务问题……）
//            throw e;
        } catch (Exception e) {
            logger.error("处理MQ消息异常！",e);
            rollback(param);
            // 抛出异常：消息重试（解决异常：代码bug、网络问题、服务问题……）
//            throw e;
        }

    }

    /**
     * 处理抽奖异常的回滚操作
     * 为了保证 事务/数据 的一致性
     * --- 操作目的就是回到之前库表的状态 ---
     *
     * @param param
     */
    private void rollback(DrawPrizeParam param) {
        // 1、回滚： 活动、奖品、人员状态
        // 判断状态是否需要回滚
        // 不需要 return;
        if (!statusNeedRollBack(param)) {
            return;
        }
        // 需要
        rollbackStatus(param);

        // 2、回滚： 中奖者名单
        // 判断状态是否需要回滚
        // 不需要 return;
        if (!winnerNeedRollBack(param)) {
            return;
        }
        // 需要
        rollbackWinner(param);
    }


    /**
     * 回滚中奖记录（删除奖品下的中奖者）
     * 
     * @param param
     */
    private void rollbackWinner(DrawPrizeParam param) {
        drawPrizeService.deleteRecords(param.getActivityId(),param.getPrizeId());
    }

    /**
     *  恢复相关状态（回滚）
     * @param param
     */
    private void rollbackStatus(DrawPrizeParam param) {
        // 涉及状态的恢复工作，使用 ActivityStatusManager
        ConvertActivityStatusDTO convertActivityStatusDTO = new ConvertActivityStatusDTO();
        convertActivityStatusDTO.setActivityId(param.getActivityId());
        convertActivityStatusDTO.setTargetActivityStatus(ActivityStatusEnum.RUNNING);
        convertActivityStatusDTO.setPrizeId(param.getPrizeId());
        convertActivityStatusDTO.setTargetPrizeStatus(ActivityPrizeStatusEnum.INIT);
        convertActivityStatusDTO.setUserIds(
                param.getWinnerList().stream()
                        .map(DrawPrizeParam.Winner::getUserId)
                        .collect(Collectors.toList())
        );
        convertActivityStatusDTO.setTargetUserStatus(ActivityUserStatusEnum.INIT);

        activityStatusManager.rollbackHandlerEvent(convertActivityStatusDTO);
    }

    private boolean winnerNeedRollBack(DrawPrizeParam param) {
        // 查看活动中的奖品有没有中奖者
        int count = winningRecordMapper.countByAPId(param.getActivityId(),param.getPrizeId());
        return count > 0;
    }

    private boolean statusNeedRollBack(DrawPrizeParam param) {
        // 判断 活动、奖品、人员相关状态是否已经扭转
        // 在扭转状态时保证了事务的一致性，要么都扭转了，要么都没有（不包括活动）
        // 因此只要判断人员/奖品 是否被扭转了就可以
        // 结论：判断奖品状态是否扭转，就能判断出全部状态是否扭转
        ActivityPrizeDO activityPrizeDO =
                activityPrizeMapper.selectByAPId(param.getActivityId(), param.getPrizeId());
        if (activityPrizeDO.getStatus()
                .equalsIgnoreCase(ActivityPrizeStatusEnum.COMPLETED.name())) {
            // 表示已经扭转，需要回滚
            return true;
        }
        return false;
    }

    /**
     * 并发处理抽奖后续流程
     *
     * @param winningRecordDOList
     */
    private void syncExecute(List<WinningRecordDO> winningRecordDOList) {
       // 通过线程池
        logger.info("syncExecute 发送短信/邮件");
       // 执行短信通知
        threadPoolTaskExecutor.execute(() -> sendMessage(winningRecordDOList));

        // 执行邮件通知
        threadPoolTaskExecutor.execute(() -> sendMail(winningRecordDOList));

    }

    // 发短信
    private void sendMessage(List<WinningRecordDO> winningRecordDOList) {
        if (CollectionUtils.isEmpty(winningRecordDOList)) {
            logger.info("中奖列表为空，无需发送短信");
            return;
        }
        for (WinningRecordDO winningRecordDO : winningRecordDOList) {
            //恭喜你，${name}！在${activityName}中得到了${prizeTiers} ，得到了${prizeName} ，请尽快来领取吧！活动时间：${winningName}
            Map<String ,String > map = new HashMap<>();
            map.put("name", winningRecordDO.getWinnerName());
            map.put("activityName", winningRecordDO.getActivityName());
            System.out.println("打印日志-------------------");
            System.out.println(winningRecordDO.getPrizeTier());
            System.out.println(ActivityPrizeTiersEnum.forName(winningRecordDO.getPrizeTier()));
            System.out.println(ActivityPrizeTiersEnum.forName(winningRecordDO.getPrizeTier()).getMessage());
            map.put("prizeTiers", ActivityPrizeTiersEnum.forName(winningRecordDO.getPrizeTier()).getMessage());
            map.put("prizeName", winningRecordDO.getPrizeName());
            map.put("winningName",DateUtil.formatTime(winningRecordDO.getWinningTime()));
            smsUtil.sendMessage("SMS_481115129",
                    winningRecordDO.getWinnerPhoneNumber().getValue(),
                    JacksonUtil.writeValueAsString(map));
        }

    }

    // 发邮件
    private void sendMail(List<WinningRecordDO> winningRecordDOList) {
        if (CollectionUtils.isEmpty(winningRecordDOList)) {
            logger.info("中奖列表为空，无需发送邮件");
            return;
        }
        for (WinningRecordDO winningRecordDO:winningRecordDOList) {
            //恭喜你，${name}！在${activityName}中得到了${prizeTiers} ，得到了${prizeName} ，请尽快来领取吧！活动时间：${winningName}
            String context = "恭喜你，"
                    + winningRecordDO.getWinnerName() +"！在"
                    + winningRecordDO.getActivityName()+"中得到了"
                    + ActivityPrizeTiersEnum.forName(winningRecordDO.getPrizeTier()).getMessage() + "，得到了"
                    + winningRecordDO.getPrizeName() + " ，请尽快来领取吧！活动时间："
                    + DateUtil.formatTime(winningRecordDO.getWinningTime());
            mailUtil.sendSampleMail(winningRecordDO.getWinnerEmail(),
                              "中奖通知",
                               context);
        }

    }

    // 状态扭转
    private void statusConvert(DrawPrizeParam param) {
        ConvertActivityStatusDTO convertActivityStatusDTO = new ConvertActivityStatusDTO();
        convertActivityStatusDTO.setActivityId(param.getActivityId());
        convertActivityStatusDTO.setTargetActivityStatus(ActivityStatusEnum.COMPLETED);
        convertActivityStatusDTO.setPrizeId(param.getPrizeId());
        convertActivityStatusDTO.setTargetPrizeStatus(ActivityPrizeStatusEnum.COMPLETED);
        convertActivityStatusDTO.setUserIds(
                param.getWinnerList().stream()
                        .map(DrawPrizeParam.Winner::getUserId)
                        .collect(Collectors.toList())
        );
        convertActivityStatusDTO.setTargetUserStatus(ActivityUserStatusEnum.COMPLETED);

        activityStatusManager.handlerEvent(convertActivityStatusDTO);
    }

//
//    private void statusConvert(DrawPrizeParam param) {
//
//        /**
//         * 这种写法的问题：
//         * 1、活动状态有依赖性，依赖奖品/人员状态，如果添加其他模块，代码更新多。导致代码维护性差
//         * 2、状态扭转条件可能会扩展，当前写法扩展性差
//         * 3、代码灵活性、扩展性、维护性极差
//         *
//         * 解决方案：设计模式（责任链设计模式、策略模式、单例模式）
//         */
//
//        // 活动：RUNNING ---》 COMPLETED （全部抽完之后变成COMPLETED）
//        // 奖品：INIT ---》 COMPLETED
//        // 人员列表：INIT ---》 COMPLETED
//
//        // 1、扭转奖品状态
//        // 查询活动关联的奖品信息
//        // 条件判断是否符合扭转奖品状态：判断当前状态是否不是 COMPLETED，如果不是，就需要扭转
//
//
//
//        // 2、扭转人员状态
//        // 查询活动关联的人员信息
//        // 条件判断是否符合扭转人员状态：判断当前状态是否不是 COMPLETED，如果不是，就需要扭转
//
//        // 3、扭转活动状态（必须在扭转奖品状态之后完成）
//        // 查询活动信息
//        // 条件判断是否符合扭转活动状态：判断当前状态是否不是 COMPLETED，如果不是，且全部奖品抽完后，才要扭转
//
//
//        // 4、更新活动完整信息的缓存
//    }
//

}
